using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Runtime.Serialization;

namespace CWrapper
{
	/// <summary>
	/// Summary description for Class1.
	/// </summary>
	public class ConsoleWrapper : IDisposable
	{
        private const int STD_READER_WAIT_TIME = 0;
		private Process _proc = null;
		private ProcessStartInfo _procStartInfo = null;
		private bool _isStarted = false;

		private string _tempCommand = string.Empty;
		private bool _writeLine = false;
		private string _endCommand;
		private System.Text.Decoder _decoder = System.Text.Encoding.Default.GetDecoder();
		private string _id = string.Empty;
		
		private int _buffers_size = 1024;

		#region Output stream control members

		private int _outCharPos = 0;
		private int _outCharLen = 0;
		private byte[] _outByteBuffer;
		private char[] _outCharBuffer;
		
		#endregion

		#region Input stream control members

		private int _errCharPos = 0;
		private int _errCharLen = 0;
		private byte[] _errByteBuffer;
		private char[] _errCharBuffer;
		
		#endregion

		public ConsoleWrapper(string appPath, string endCommand, bool redirectStandardInput, bool redirectStandardOutput, bool redirectStandardError)
		{
			_procStartInfo = new ProcessStartInfo();
			_procStartInfo.FileName = appPath;
			_procStartInfo.RedirectStandardInput = redirectStandardInput;
			_procStartInfo.RedirectStandardOutput = redirectStandardOutput;
			_procStartInfo.RedirectStandardError = redirectStandardError;

			_procStartInfo.UseShellExecute = false;
			_procStartInfo.CreateNoWindow = true;

			_endCommand = endCommand;
		}

		#region Pre-Start configuration

        public string EndCommand
        {
            get
            {
                return _endCommand;
            }
        }
        
        public int BuffersSize{
			get{
			   return _buffers_size;
			}
			set{
				_buffers_size = value;
			}
		}

		public string ConsoleId{
			get{
				return _id;
			}
			set{
				_id = value;
			}
		}

		public bool HasExited{
			get{
				try{
					if(_proc == null)
						return true;
					else
						return _proc.HasExited;
				}
				catch{
					return false;
				}
			}
		}
		
		public void AddEnvironmentVariable(string name, string envValue)
		{
            if (!_isStarted && !_procStartInfo.EnvironmentVariables.ContainsKey(name))
            {
				_procStartInfo.EnvironmentVariables.Add(name, envValue);
			}
		}

		public string ConsoleAppPath{
			get{
				return _procStartInfo.FileName;
			}
			set{
				_procStartInfo.FileName = value;
			}
		}

		public string Arguments
		{
			get{
				return _procStartInfo.Arguments;
			}
			set{
				_procStartInfo.Arguments = value;
			}
		}

		#endregion

		#region Start

		public void StartWrapper(int millisecondsToWait, int bufferSize){
			_buffers_size = bufferSize;
			_outCharBuffer = new char[_buffers_size];
			_outByteBuffer = new byte[_buffers_size];
			_errCharBuffer = new char[_buffers_size];
			_errByteBuffer = new byte[_buffers_size];
			_proc = Process.Start(_procStartInfo);
			System.Threading.Thread.Sleep(millisecondsToWait);
			_outCharPos = 0;
			_errCharPos = 0;
			_isStarted = true;
		}

		#endregion

		#region Post-Start functionalities

		[MethodImpl(MethodImplOptions.Synchronized)]
		private void ExecuteCommand()
		{
			if(_writeLine)
				_proc.StandardInput.WriteLine(_tempCommand);
			else
				_proc.StandardInput.Write(_tempCommand);
		}
		
		public void WriteLineToStdInput(string command){
			_writeLine = true;
			_tempCommand = command;
			ThreadStart tStart = new ThreadStart(ExecuteCommand);
			Thread t = new Thread(tStart);
			t.Start();
		}

		public void WriteToStdInput(string command){
			_writeLine = false;
			_tempCommand = command;
			ThreadStart tStart = new ThreadStart(ExecuteCommand);
			Thread t = new Thread(tStart);
			t.Start();
		}

		public void DiscardBufferedStdOutData()
		{
			_outCharLen = 0;
			_outCharPos = 0;

			//DEPRECATED
			//_outCharBuffer = new char[_buffers_size];
			//_outByteBuffer = new byte[_buffers_size];
			_proc.StandardOutput.DiscardBufferedData();
		}

		public void DiscardBufferedStdErrData(){
			_errCharLen = 0;
			_errCharPos = 0;
			//DEPRECATED
			//_errCharBuffer = new char[_buffers_size];
			//_errByteBuffer = new byte[_buffers_size];
			_proc.StandardError.DiscardBufferedData();
		}

		public char ReadCharFromStdOut(bool consume)
		{
			if(NextStdOuputIsAvailable())
			{
				if(consume)
				{
					if (_outCharPos == _outCharLen)
					{
						return new char();
					}
					return Convert.ToChar(_outCharBuffer[_outCharPos++]);
				}
				else
					return Convert.ToChar(PeekStdOut());
			}
			return new char();
		}

		public char ReadCharFromStdErr(bool consume)
		{
			if(NextStdErrIsAvailable())
			{
				if(consume)
				{
					if (_errCharPos == _errCharLen)
					{
						return new char();
					}
					return Convert.ToChar(_errCharBuffer[_errCharPos++]);
				}
				else
					return Convert.ToChar(PeekStdErr());
			}
			return new char();
		}

		public bool NextStdOuputIsAvailable()
		{
			int peek = PeekStdOut();
			return (peek!=-1/* && peek!=0*/);
		}

		public bool NextStdErrIsAvailable()
		{
			int peek = PeekStdErr();
			return (peek!=-1/* && peek!=0*/);
		}

		private int PeekStdOut()
		{
			if ((_outCharPos != _outCharLen && _outCharPos != _outCharBuffer.Length))
			{
				return _outCharBuffer[_outCharPos];
			}
			return -1;
		}

		private int PeekStdErr()
		{
			if ((_errCharPos != _errCharLen && _errCharPos != _errCharBuffer.Length))
			{
				return _errCharBuffer[_errCharPos];
			}
			return -1;
		}

		public void ReadStdOutBuffer(bool isAsync)
		{	
			if(isAsync)
			{
				AsyncCallback readOutCallback = new AsyncCallback(ReadStdOutCallback);
				_proc.StandardOutput.BaseStream.BeginRead(_outByteBuffer, 0, _buffers_size, readOutCallback,"ejej");
			}
			else
			{
				int byteLen = _proc.StandardOutput.BaseStream.Read(_outByteBuffer, 0, _buffers_size);
				_outCharLen += _decoder.GetChars(_outByteBuffer, 0, byteLen, _outCharBuffer, _outCharLen);
			}
			Wait(STD_READER_WAIT_TIME);
		}

		public void ReadStdErrBuffer(bool isAsync)
		{	
			if(isAsync)
			{
				AsyncCallback readErrCallback = new AsyncCallback(ReadStdErrCallback);
				_proc.StandardError.BaseStream.BeginRead(_errByteBuffer, 0, _buffers_size, readErrCallback,"ejej");
			}
			else
			{
				int byteLen = _proc.StandardError.BaseStream.Read(_errByteBuffer, 0, _buffers_size);
				_errCharLen += _decoder.GetChars(_errByteBuffer, 0, byteLen, _errCharBuffer, _errCharLen);
			}
			Wait(STD_READER_WAIT_TIME);
		}
		
		public void ReadStdOutCallback(IAsyncResult asyncResult)
		{
			int byteLen = _proc.StandardOutput.BaseStream.EndRead(asyncResult);
			_outCharLen += _decoder.GetChars(_outByteBuffer, 0, byteLen, _outCharBuffer, _outCharLen);
		}

		public void ReadStdErrCallback(IAsyncResult asyncResult)
		{
			int byteLen = _proc.StandardError.BaseStream.EndRead(asyncResult);
			_errCharLen += _decoder.GetChars(_errByteBuffer, 0, byteLen, _errCharBuffer, _errCharLen);
		}

		public void KillWrapper(){
			try
			{
				if(!_proc.HasExited)
				{
					WriteLineToStdInput(_endCommand);
					Wait(100);
				
					_proc.Kill();
                    _isStarted = false;
				}
			}
			catch{}
		}

		public StreamReader StandardError
		{
			get
			{
				return _proc.StandardError;
			}
		}

		public void Wait(int milliseconds)
		{
			System.Threading.Thread.Sleep(milliseconds);
		}

        public bool IsStarted{
            get {
                return _isStarted;
            }
        }

		#endregion

		#region IDisposable Members

		public void Dispose() 
		{
			_proc.Dispose();
		}
		#endregion
	}
}
